#define _XOPEN_SOURCE 500

#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include "scutable.h"

#undef LOCK
#undef UNLOCK
#undef UNLOCKRETI
#undef UNLOCKRETP

#ifdef SCU_TABLE_LOCKING
#define LOCK(tbl)               scos_mutex_lock(&tbl->lock)
#define UNLOCK(tbl)             scos_mutex_unlock(&tbl->lock)
#define UNLOCKRETI(tbl, retval) { int _retval = retval; UNLOCK(tbl); return _retval; }
#define UNLOCKRETP(tbl, retval) { void *_retval = retval; UNLOCK(tbl); return _retval; }
#else
#define LOCK(tbl)
#define UNLOCK(tbl)
#define UNLOCKRETI(tbl, retval) { return retval; }
#define UNLOCKRETP(tbl, retval) { return retval; }
#endif /* SCU_TABLE_LOCKING */


int tblcreate(table_t *tbl, int size)
{
  tbl->size = size;
  tbl->length = 1;
  tbl->elements = 0;
  tbl->hashlength = 0;
  tbl->hash = NULL;
  
  if (!(tbl->data = (void**) malloc(tbl->length*sizeof(void*))))
    return -ENOMEM;
  memset(tbl->data, 0, tbl->length*sizeof(void*));

#ifdef SCU_TABLE_LOCKING
  scos_mutex_init(&tbl->lock);
#endif

  return 0;
}

int tbldestroy(table_t *tbl, int dofree)
{
  int i;

  LOCK(tbl);

  for (i=0; i < tbl->length; i++)
    if (tbl->data[i])
      tbldel(tbl, i, dofree);

  free(tbl->data);
  tbl->data = NULL;
  tbl->size = tbl->length = 0;

  UNLOCK(tbl);
#ifdef SCU_TABLE_LOCKING
  scos_mutex_destroy(&tbl->lock);
#endif  

  return 0;
}

int tblclear(table_t *tbl, int dofree)
{
  int i;

  LOCK(tbl);

  for (i=0; i < tbl->length; i++)
    if (tbl->data[i])
      tbldel(tbl, i, dofree);

  UNLOCKRETI(tbl, 0);
}

int _tbladd(table_t *tbl, void *data)
{
  int i;

  LOCK(tbl);

  for (i=0; i < tbl->length; i++)
    if (!tbl->data[i])
      break;

  UNLOCKRETI(tbl, _tbladdi(tbl, data, i));
}

int _tbladdi(table_t *tbl, void *data, int i)
{
  int ii;
  void **newdata;

  LOCK(tbl);

  while (i >= tbl->length)
  {
    tbl->length = tbl->length * 2;
    if (!(newdata = (void**) realloc(tbl->data, tbl->length*sizeof(void**))))
      UNLOCKRETI(tbl, -ENOMEM);

    tbl->data = newdata;

    for (ii=tbl->length/2; ii < tbl->length; ii++)
      tbl->data[ii] = NULL;
  }  

  if (tbl->data[i])
    UNLOCKRETI(tbl, -EEXIST);

  tbl->data[i] = data;

  tbl->elements++;

  UNLOCKRETI(tbl, i);
}

int _tbladdnew(table_t *tbl, void **data)
{
  int retval;
  void *newdata;

  LOCK(tbl);

  if (!(newdata = calloc(1, tbl->size)))
    UNLOCKRETI(tbl, -ENOMEM);

  if ((retval = _tbladd(tbl, newdata)) < 0)
    free(newdata);

  if (data)
    *data = newdata;

  UNLOCKRETI(tbl, retval);
}

int _tbladdnewi(table_t *tbl, void **data, int i)
{
  int retval;
  void *newdata;

  LOCK(tbl);

  if (!(newdata = malloc(tbl->size)))
    UNLOCKRETI(tbl, -ENOMEM);
  memset(newdata, 0, tbl->size);

  if ((retval = _tbladdi(tbl, newdata, i)) < 0)
    free(newdata);

  if (data)
    *data = newdata;  

  UNLOCKRETI(tbl, retval);
}

int tbldel(table_t *tbl, int i, int dofree)
{
  LOCK(tbl);

  if (i < 0 || i >= tbl->length)
    UNLOCKRETI(tbl, -EINVAL);

  if (dofree)
    free(tbl->data[i]);

  tbl->data[i] = NULL;

  tbl->elements--;

  UNLOCKRETI(tbl, i);
}

void *_tblget(table_t *tbl, int i, void** data)
{
  LOCK(tbl);

  if (i < 0 || i >= tbl->length)
  {
    if (data)
      *data = NULL;
    UNLOCKRETP(tbl, NULL);
  }

  if (data)
    *data = tbl->data[i];

  UNLOCKRETP(tbl, tbl->data[i]);
}

int tblgetnexti(table_t *tbl, int i)
{
  LOCK(tbl);

  if (i < 0 || i >= tbl->length)
    i = -1;

  for (i++; i < tbl->length; i++)
    if (tbl->data[i])
      break;

  if (i >= tbl->length)
    UNLOCKRETI(tbl, -ENOENT);

  UNLOCKRETI(tbl, i);
}

void *_tblgetnextelement(table_t *tbl, int *i, void **data)
{
  int _i=*i;

  LOCK(tbl);

  if (_i < 0 || _i >= tbl->length)
    _i = -1;

  for (_i++; _i < tbl->length; _i++)
    if (tbl->data[_i])
      break;

  if (_i >= tbl->length)
  {
    *i = -ENOENT;
    if (data)
      *data = NULL;
    UNLOCKRETP(tbl, NULL);
  }

  if (data)
    *data = tbl->data[_i];

  UNLOCKRETP(tbl, tbl->data[*i=_i]);
}

int tblgetelements(table_t *tbl)
{
  LOCK(tbl);

  UNLOCKRETI(tbl, tbl->elements);
}

int tblcreatehash(table_t *tbl, int size, int length)
{
  LOCK(tbl);
  
  if (length <= 0)
    UNLOCKRETI(tbl, -EINVAL);

  tbl->hashlength = length;
  tbl->hashsize = size;

  if (!(tbl->hash = (table_hash_t**) calloc(tbl->hashlength, sizeof(table_hash_t*))))
    UNLOCKRETI(tbl, -ENOMEM); 
  
  UNLOCKRETI(tbl, 0);
}

int _tbldohash(table_t *tbl, void *data)
{
  int i, hash;

  LOCK(tbl);  

  if (tbl->hashlength <= 0 || tbl->hashsize <= 0 || !data)
    UNLOCKRETI(tbl, -EINVAL);

  for (i=0, hash = 0; i < tbl->hashsize; i++)
    hash = (hash + *((unsigned char*)data+i)) % tbl->hashlength;

  UNLOCKRETI(tbl, hash);
}

int tbladdhash(table_t *tbl, void *data, int i)
{
  int hash;
  table_hash_t *h;

  LOCK(tbl);

  if (tbl->hashlength <=0 || tbl->hashsize <= 0 || !tbl->hash || !data)
    UNLOCKRETI(tbl, -EINVAL);

  if ((hash = _tbldohash(tbl, data)) < 0)
    UNLOCKRETI(tbl, hash);

  if (!(h = tbl->hash[hash]))
  {
    if (!(h = tbl->hash[hash] = (table_hash_t*) malloc(sizeof(table_hash_t))))
      	UNLOCKRETI(tbl, -ENOMEM);
  }
  else
  {
    while (h->next)
      h = h->next;
    
     if (!(h->next = (table_hash_t*) malloc(sizeof(table_hash_t))))
	UNLOCKRETI(tbl, -ENOMEM);
     h = h->next;
  }

  if (!(h->data = malloc(tbl->hashsize)))
    UNLOCKRETI(tbl, -ENOMEM);
  
  memcpy(h->data, data, tbl->hashsize);
  h->i = i;
  h->next = NULL;

//printf("tbladdhash: %p -> %d\n", *((int*)h->data), h->i);

  UNLOCKRETI(tbl, 0);
}

int tblfindhash(table_t *tbl, void *data)
{
  int hash;
  table_hash_t *h;

  if (!tbl)
    return -EINVAL;

  LOCK(tbl);

  if (tbl->hashlength <=0 || tbl->hashsize <= 0 || !tbl->hash || !data)
    UNLOCKRETI(tbl, -EINVAL);

  if ((hash =  _tbldohash(tbl, data)) < 0)
    UNLOCKRETI(tbl, hash);

  h = tbl->hash[hash];
  while (h && memcmp(h->data, data, tbl->hashsize))
    h = h->next;

  if (!h)
    UNLOCKRETI(tbl, -ENOENT);
  
//printf("tblfindhash: %p -> %d\n", *((int*)h->data), h->i);

  UNLOCKRETI(tbl, h->i);
}

int tbldelhash(table_t *tbl, void *data)
{
  int hash;
  table_hash_t *h, *p;

  if (!tbl)
    return -EINVAL;

  LOCK(tbl);

  if (tbl->hashlength <=0 || tbl->hashsize <= 0 || !tbl->hash || !data)
    UNLOCKRETI(tbl, -EINVAL);

  if ((hash = _tbldohash(tbl, data)) < 0)
    UNLOCKRETI(tbl, hash);

  p = NULL;
  h = tbl->hash[hash];
  while (h && memcmp(h->data, data, tbl->hashsize))
  {
    p = h;
    h = h->next;
  }

  if (!h)
    UNLOCKRETI(tbl, -ENOENT);

  if (!p)
    tbl->hash[hash] = h->next;
  else
    p->next = h->next;

  free(h->data);
  free(h);
  
  UNLOCKRETI(tbl, 0);
}

int tbldestroyhash(table_t *tbl)
{
  int i;
  table_hash_t *h, *temp;
  
  if (!tbl)
    return -EINVAL;

  LOCK(tbl);

  if (tbl->hashlength <=0 || !tbl->hash)
    UNLOCKRETI(tbl, -EINVAL);

  for (i=0; i < tbl->hashlength; i++)
  {
    h = tbl->hash[i];
    while (h)
    {
      temp = h;
      h = h->next;
      
      free(temp->data);
      free(temp);
    }
  }

  free(tbl->hash);
  tbl->hash = NULL;  

  UNLOCKRETI(tbl, 0);
}

int tblsort(table_t *tbl, int(*compar)(const void*, const void*))
{
  if (!tbl)
    return -EINVAL;

  LOCK(tbl);

  if (tbl->length <= 0 || tbl->size <= 0 || !tbl->data)

    UNLOCKRETI(tbl, -EINVAL);

  /* NOTE: DOES NOT RENUMBER HASHES */
  qsort(tbl->data, tbl->length, sizeof(void*), compar);

  UNLOCKRETI(tbl, 0);
}

int tblcopy(table_t *dst, table_t *src, int dofree)
{
  int id=-1, retval;
  void *item;

  if (!src || !dst)
    return -EINVAL;

  LOCK(src);
  LOCK(dst);

  retval = tblclear(dst, dofree);
  if (retval < 0)
  {
    UNLOCK(dst);
    UNLOCK(src);
    return retval;
  }
  
  while (tblget(src, id = tblgetnexti(src, id), &item))
  {
    retval = _tbladdi(dst, item, id);
    if (retval < 0)
    {
      UNLOCK(dst);
      UNLOCK(src);
      return retval;
    }
  }

  UNLOCK(dst);
  UNLOCK(src);
  return 0;
}
